//  -------------------- iSharePoint -------------------- //
// 
//  Company:	IT Complex, LLC
//  Project:	Smart.Common
//  File name:	EntityMapperExts.cs
//  Developer:	Solomatov Igor
//  Created:	15.04.2012
// 
//  -------------------- iSharePoint -------------------- //

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Web;
using Microsoft.SharePoint;
using Smart.Common.Entities.Mapper;

namespace Smart.Common.Entities
{
	/// <summary>
	/// Entity Mapper extensions
	/// </summary>
	public static class EntityMapperExts
	{
		static EntityMapperExts()
		{
		}

		private static SyncDictionary<string, FieldMapperCacheList> _cacheMeta = new SyncDictionary<string, FieldMapperCacheList>(StringComparer.OrdinalIgnoreCase);
		private static SyncDictionary<string, IEntityMapper> _cacheMapper = new SyncDictionary<string, IEntityMapper>(StringComparer.OrdinalIgnoreCase);

		public static FieldMapperCacheList ReadCacheMeta(string typeName, Type et)
		{
			FieldMapperCacheList mapping = null;

			if (!_cacheMeta.TryGetValue(typeName, out mapping))
			{
				mapping = new FieldMapperCacheList();

				var etps = et.GetProperties();

				foreach (var etp in etps)
				{
					var etpas = etp.GetCustomAttributes(typeof(FieldAttribute), true);

					if (etpas != null && etpas.Length > 0)
					{
						var etpa = (FieldAttribute)etpas[0];

						if (etpa != null)
						{
							var ci = new FieldMapperCacheItem { FieldName = etpa.FieldName, MappingType = etpa.MappingType, Direction = etpa.Direction, Property = etp };

							if (ci.MappingType == FieldMappingType.Auto)
							{
								if (etp.PropertyType == typeof(string))
								{
									ci.MappingType = FieldMappingType.String;
								}
								else if (etp.PropertyType == typeof(LookupValue))
								{
									ci.MappingType = FieldMappingType.Lookup;
								}
								else if (etp.PropertyType == typeof(DateTime))
								{
									ci.MappingType = FieldMappingType.DateTime;
								}
								else if (etp.PropertyType == typeof(Double))
								{
									ci.MappingType = FieldMappingType.Double;
								}
								else if (etp.PropertyType == typeof(LookupValueCollection))
								{
									ci.MappingType = FieldMappingType.LookupMulti;
								}
								else if (etp.PropertyType == typeof(UserValue))
								{
									ci.MappingType = FieldMappingType.User;
								}
								else if (etp.PropertyType == typeof(UserValueCollection))
								{
									ci.MappingType = FieldMappingType.UserMulti;
								}
								else if (etp.PropertyType == typeof(bool))
								{
									ci.MappingType = FieldMappingType.Bool;
								}
								else if (etp.PropertyType == typeof(LinkValue))
								{
									ci.MappingType = FieldMappingType.Link;
								}
								else if (etp.PropertyType == typeof(ChoiceValueCollection))
								{
									ci.MappingType = FieldMappingType.ChoiceMulti;
								}
								else if (etp.PropertyType == typeof(int))
								{
									ci.MappingType = FieldMappingType.Int;
								}
								else if (etp.PropertyType == typeof(Identifier))
								{
									ci.MappingType = FieldMappingType.Identifier;
								}
							}

							mapping.Add(ci);
						}
					}
				}

				_cacheMeta[typeName] = mapping;
			}

			return mapping;
		}

		public static IEntityMapper ReadCacheMapper(string typeName, Type et, Func<MapperClassBuilderConfig> getConfig)
		{
			IEntityMapper mapper = null;

			if (!_cacheMapper.TryGetValue(typeName, out mapper))
			{
				mapper = MapperClassBuilder.Generate(et, getConfig(), ReadCacheMeta(typeName, et));

				_cacheMapper[typeName] = mapper;
			}

			return mapper;
		}

		public static bool GetEntityValues(this Entity entity, Dictionary<string, object> values)
		{
			var et = entity.GetType();

			var typeName = et.FullName;

			var mapping = ReadCacheMeta(typeName, et);

			foreach (var fmi in mapping)
			{
				values[fmi.FieldName] = fmi.Property.GetValue(entity, null);
			}

			return true;
		}

		public static void GetUserValues(this Dictionary<string, object> src, Dictionary<string, object> dest, CultureInfo culture)
		{
			if (src == null || dest == null)
				return;

			foreach (var kvp in src)
			{
				var k1 = kvp.Key;
				var v1 = kvp.Value;

				string k2 = null;
				object v2 = null;

				if (v1 != null)
				{
					if (v1 is int)
					{
						var lv = (int)kvp.Value;

						v1 = lv.ToString(culture);
					}
					else if (v1 is double)
					{
						var lv = (double)kvp.Value;

						v1 = lv.ToString(culture);
					}
					else if (v1 is DateTime)
					{
						var lv = (DateTime)kvp.Value;

						v1 = lv.ToShortDateString();
					}
					else if (v1 is SPFieldUserValue)
					{
						var uv = (SPFieldUserValue)kvp.Value;

						v1 = uv.LookupValue;

						k2 = k1 + ".ID";
						v2 = uv.LookupId.ToString(culture);
					}
					else if (v1 is SPFieldUserValueCollection)
					{
						var uvc = (SPFieldUserValueCollection)kvp.Value;

						v1 = string.Join(string.Empty, uvc.Select(lv => lv.LookupValue + SPH.MultiLookupValueDelimiter).ToArray());

						k2 = k1 + ".ID";
						v2 = string.Join(string.Empty, uvc.Select(uv => uv.LookupId.ToString(culture) + SPH.MultiLookupValueDelimiter).ToArray());
					}
					else if (v1 is SPFieldLookupValue)
					{
						var lv = (SPFieldLookupValue)kvp.Value;

						v1 = lv.LookupValue;

						k2 = k1 + ".ID";
						v2 = lv.LookupId.ToString(culture);
					}
					else if (v1 is SPFieldLookupValueCollection)
					{
						var lvc = (SPFieldLookupValueCollection)kvp.Value;

						v1 = string.Join(string.Empty, lvc.Select(lv => lv.LookupValue + SPH.MultiLookupValueDelimiter).ToArray());

						k2 = k1 + ".ID";
						v2 = string.Join(string.Empty, lvc.Select(lv => lv.LookupId.ToString(culture) + SPH.MultiLookupValueDelimiter).ToArray());
					}
					else if (v1 is SPFieldMultiChoiceValue)
					{
						var cvcv = (SPFieldMultiChoiceValue)kvp.Value;

						var cvc = new List<string>();

						for (int i = 0, iCnt = cvcv.Count; i < iCnt; i++)
							cvc.Add(cvcv[i]);

						v1 = string.Join(string.Empty, cvc.Select(s => s + SPH.MultiLookupValueDelimiter).ToArray());
					}
					else if (v1 is LookupValue)
					{
						var lv = (LookupValue)kvp.Value;

						v1 = lv.Title;

						k2 = k1 + ".ID";
						v2 = lv.ID.ToString();
					}
					else if (v1 is LookupValueCollection)
					{
						var lvc = (LookupValueCollection)kvp.Value;

						v1 = string.Join(string.Empty, lvc.Select(lv => lv.Title + SPH.MultiLookupValueDelimiter).ToArray());

						k2 = k1 + ".ID";
						v2 = string.Join(string.Empty, lvc.Select(lv => lv.ID.ToString() + SPH.MultiLookupValueDelimiter).ToArray());
					}
					else if (v1 is UserValue)
					{
						var uv = (UserValue)kvp.Value;

						v1 = uv.Title;

						k2 = k1 + ".ID";
						v2 = uv.ID.ToString();
					}
					else if (v1 is UserValueCollection)
					{
						var uvc = (UserValueCollection)kvp.Value;

						v1 = string.Join(string.Empty, uvc.Select(lv => lv.Title + SPH.MultiLookupValueDelimiter).ToArray());

						k2 = k1 + ".ID";
						v2 = string.Join(string.Empty, uvc.Select(uv => uv.ID.ToString() + SPH.MultiLookupValueDelimiter).ToArray());
					}
					else if (v1 is ChoiceValueCollection)
					{
						var cvc = (ChoiceValueCollection)kvp.Value;

						v1 = string.Join(string.Empty, cvc.Select(s => s + SPH.MultiLookupValueDelimiter).ToArray());
					}
					else if (v1 is LinkValue)
					{
						var lv = (LinkValue)kvp.Value;

						v1 = lv.Url;

						k2 = k1 + ".Title";
						v2 = lv.Title;
					}
				}

				if (k1 != null)
					dest[k1] = v1;

				if (k2 != null)
					dest[k2] = v2;
			}
		}

		public static bool SetEntityValues(this Entity entity, Dictionary<string, object> values)
		{
			var et = entity.GetType();

			var typeName = et.FullName;

			var mapping = ReadCacheMeta(typeName, et);

			foreach (var fmi in mapping)
			{
				if (fmi.Direction == FieldMappingDirection.In)
					continue;

				fmi.Property.SetValue(entity, values.SafeGet(fmi.FieldName), null);
			}

			return true;
		}

		public static bool GetEntityValue(this Entity entity, string fieldName, out object value)
		{
			var et = entity.GetType();

			var typeName = et.FullName;

			var mapping = ReadCacheMeta(typeName, et);

			var dfmi = mapping.GetDictionary();

			var fmi = dfmi.SafeGet(fieldName);

			if (fmi == null)
				throw new ArgumentOutOfRangeException("fieldName: " + fieldName);

			value = fmi.Property.GetValue(entity, null);

			return true;
		}

		public static bool SetEntityValue(this Entity entity, string fieldName, object value)
		{
			var et = entity.GetType();

			var typeName = et.FullName;

			var mapping = ReadCacheMeta(typeName, et);

			var dfmi = mapping.GetDictionary();

			var fmi = dfmi.SafeGet(fieldName);

			if (fmi == null || fmi.Direction == FieldMappingDirection.In)
				throw new ArgumentOutOfRangeException("fieldName: " + fieldName);

			fmi.Property.SetValue(entity, value, null);

			return true;
		}
	}
}